util = require("util")
http = require("http")
https = require("https")
urlparse = require('url').parse
qstr = require('querystring')
config = require '../config'
assert = require('assert').ok
express = require("express")
fs = require("fs")
isArray = require("util").isArray
Q = require("q")
request = require("request")
_ = require("underscore")
uuid = require("node-uuid")
os = require('os')
crypto = require('crypto')

g_error_prompt_regexp = [/(\[\d+\]\[[^\[]*\])/m, /.*\[\d+\]\s*([^\[]*).*/, /([^\[]+)\[.*=.*\]/m]

exports.retrieve_error_prompt = retrieve_error_prompt = (msg) ->
  
  unless typeof(msg) == "string"
    return JSON.stringify msg

  for x in g_error_prompt_regexp
    r = x.exec(msg)
    if r
      return r[1]
      
  return msg


reqDesc = (method,url,params,headers,content_type) ->
  
    [pathname,params] = params if params instanceof Array
    if method == 'POST'
        options = urlparse(url)
        if pathname?
            options.pathname += pathname
            options.path += pathname

        options.method = 'POST'

        if content_type == "json"
          body = JSON.stringify(params)
        else
          #body需要转换成www/form-urlencoded格式
          body = qstr.stringify(params)

        options.headers = headers or {}
        options.headers["Content-Length"] = Buffer.byteLength(body, 'utf8')
        return { 'options': options, 'body': body }

    if method == 'GET'
        options =urlparse(url)
        if pathname?
            options.pathname += pathname
            options.path += pathname
        qdict = if options.query then qstr.parse(options.query) else { }
        qdict[k] = params[k] for k of params

        if headers
          options.headers = headers
  
        q = qstr.stringify(qdict)
        if !q then return { 'options': options }
        return { 'options':"#{options.protocol}//#{options.host}#{options.pathname}?#{q}" }

    if method == 'DELETE'
        options =urlparse(url)
        if pathname?
            options.pathname += pathname
            options.path += pathname
        options.method = 'DELETE'
                    
        qdict = if options.query then qstr.parse(options.query) else { }
        qdict[k] = params[k] for k of params
        options.query = qstr.stringify(qdict)

        if headers
          options.headers = headers
        return {options}

    throw "Unsupported method:"+method

is_req_fail = (content) ->
  return (content.success? && !content.success) or (content.error_no? and content.error_info and (content.error_no != 0 and content.error_no != "0"))

resProc = (callback,parse_json) ->
    return (resp) ->
        try
            content=""
            
            resp.setEncoding('utf8')

            resp.on "data", (chunk) ->
                content += chunk

            resp.on "end", ->

              return callback("请求失败",null) unless content
              
              if parse_json
                try
                  content = JSON.parse(content)
                catch e
                  return callback("返回格式错误:#{content}",null)

              unless content or content == false
                return callback("请求失败:#{content}",null)


              unless content.error_info   # 某些请求返回 error_description
                if content.error_description
                  content.error_info = content.error_description
              
              if resp.statusCode != 200
                if content?.error_info
                  return callback({message: retrieve_error_prompt(content.error_info) or content.error_info},null)
                else                
                  return callback({ message:'statusCode='+resp.statusCode }, content)                
                
              if is_req_fail(content)
                if content.error_info? && content.error_info
                  callback({message: retrieve_error_prompt(content.error_info) or content.error_info},null)
                else
                  callback({message: content.note ? '请求失败'},null)
              else
                  callback(null,content)
                    
        catch exception
            console.log("doHttp exception: %s", util.inspect(exception))
            callback(exception,null)


exports.hide_password = hide_password = (str) ->

  p = /(password)|(passwd)/

  if isArray(str)
    for x,i in str
      str[i] = hide_password(x)
    return str
  else if str and typeof(str) == "object"
    for k,v of str
      if typeof(k) == "string" and k.match(p)
        str[k] = "******"
      else
        str[k] = hide_password(v)
    return str
  else if typeof(str) == "string"
    if str.match(p)
      return  str.replace(/password(.?)=[^&]*/g,"password\$1=******")
                 .replace(/passwd(.?)=[^&]*/g,"passwd\$1=******")
                 .replace(/"[^"]{16,}"/g,"******")
    else if str.indexOf("00*00*00*00*00*00") >= 0
      return "******"
    else
      return str
  else
    return str


  
    
exports.parse_params = (req,mand,option,constant={}) ->
  missing = []
  result = {}

  for k in mand
    v = req.param(k)
    if v == null or v == undefined
      missing.push k
    else
      result[k] = v
  if missing.length > 0
    return "缺少必选参数: #{missing.join(',')}"
  for k in option
    v = req.param(k)
    unless v == null or v == undefined
      result[k] = v
  for own k,v of constant
    result[k] = v
  return result

exports.call_livebos = call_livebos = (function_id,body,cb) ->
  livebos = config.services.livebos
  params =
    user_id: livebos.user_id
    user_passwd: livebos.user_passwd
    function_id: function_id
    request_body: body
  http_request "POST",config.services.livebos.call,params,cb,null,"json"



exports.qcall_livebos = (function_id,body) ->
  Q.nfcall(call_livebos,function_id,body)
    .then (result) ->
      unless result
        throw get_error_info("livebos_call_error",function_id)
      unless result.error_no in [0,"0"]
        throw get_error_info("livebos_call_error", result.error_info)
      result

exports.call_livebos2 = call_livebos2 = (function_id,body,cb) ->
  livebos = config.services.livebos
  params =
    user_id: livebos.user_id
    user_passwd: livebos.user_passwd
    request_body: body
  http_request "POST",config.services.livebos.call+"/"+function_id ,params,cb,null,"json"



exports.qcall_livebos2 = (function_id,body) ->
  Q.nfcall(call_livebos2,function_id,body)
    .then (result) ->
      unless result
        throw get_error_info("livebos_call_error",function_id)
      unless result.error_no in [0,"0"]
        throw get_error_info("livebos_call_error", result.error_info)
      result
  

g_server_port = 3000
exports.set_server_port = (port) -> g_server_port=port

exports.http_request = http_request = (method,url,params,callback,headers=null,content_type="x-www-form-urlencoded",parse_json=true,timeout=30000) ->

        t0 = new Date().getTime()
        options = ''
        cb = (err,resp) ->
          console.log "[COST: %d millisecs]%s", (new Date().getTime()) - t0, hide_password(options)
          if err
            console.error util.inspect {type:"http_request",method,url,params:hide_password(params),headers,error:err,response:resp}
          callback(err,resp)

        if url[0] == '/'  # 本地调用
          url = "http://localhost:#{g_server_port}#{url}"

        if url.indexOf("https") == 0
          sender = https
        else
          sender = http

        try
            if method == 'GET'
                desc = reqDesc('GET',url,params,headers,content_type)
                if (typeof desc.options) == "string"
                  options = "[GET:#{desc.options}]"
                else
                  options = "[GET:#{desc.options?.href}:#{desc.options?.path}][DATA:#{desc.query}]"
                req=sender.get(desc.options,resProc(cb,parse_json))
                req.setTimeout timeout, () ->
                  cb("timeout",null)
                  req.abort()
                req.on 'error', (err) ->
                    console.log("doHttp error: %s", util.inspect(err))
                    cb(err,null)
            else if method == "DELETE"
                desc = reqDesc('DELETE',url,params,headers,content_type)
                options = "[DELETE:#{JSON.stringify desc.options}]"
                req=sender.request(desc.options,resProc(cb,parse_json))
                req.setTimeout timeout, () ->
                  cb("timeout",null)
                  req.abort()
                req.on 'error', (err) ->
                    console.log("doHttp error: %s", util.inspect(err))
                    cb(err,null)
                if content_type == "json"
                  req.setHeader("Content-Type","application/json; charset=UTF-8")
                else
                  req.setHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8")
                req.end()
            else if method == 'POST'
                desc = reqDesc('POST',url,params,headers,content_type)
                options = "[POST:#{desc.options?.href}:#{desc.options?.path}][DATA:#{desc.body}]"
                req=sender.request(desc.options,resProc(cb,parse_json))
                req.setTimeout timeout, () ->
                  cb("timeout",null)
                  req.abort()
                req.on 'error', (err) ->
                    console.log("doHttp error: %s", util.inspect(err))
                    cb(err,null)

                if content_type == "json"
                  req.setHeader("Content-Type","application/json; charset=UTF-8")
                else
                  req.setHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8")
  
                req.write(desc.body)
                req.end()
            else
                throw  "Unsupported method:"+method
        catch exception
            console.log("doHttp exception: %s", util.inspect(exception))
            cb(exception,null)


g_http_server = null

exports.set_http_server = (server) ->
  g_http_server = server

exports.http_request_self = http_request_self = (method,url,params,headers,callback,content_type="x-www-form-urlencoded",parse_json=true) ->

  assert(g_http_server)
  
  t0 = new Date().getTime()
  options = ''
  
  cb = (err,resp) ->
    
      console.log "[COST: %d millisecs]%s", (new Date().getTime()) - t0, options

      if err
        return callback err
      unless resp
        return callback "http请求返回空"
        
      if parse_json
        try
          if typeof(resp) == "string"
            obj = JSON.parse(resp)
          else 
            obj = resp
        catch error
          return callback error

        if obj?.error
          return callback obj.error
        else if obj?.error_no != 0 and obj.error_info
          return callback retrieve_error_prompt(obj.error_info) or obj.error_info
        else
          return callback(err,obj)
      else
        return callback(err,resp)

  res = 
    send: (status, msg) ->
      if typeof msg == "object" and msg.error
        cb msg.error
      else
        cb null, msg
    json: (obj) ->
      cb null, obj
    jsonp: (obj) ->
      cb null, obj
    headers: {}
    __proto__: express.response

  try
      if method == 'GET'
          desc = reqDesc('GET',url,params,headers,content_type)
          options = "[GET:#{desc.options}]"
          req= new http.IncomingMessage
          req.ips = ["127.0.0.1"]
          req.url = url
          req.method = method
          req.query = params
          req.headers = headers
          req.on 'error', (err) ->
              console.log("http_request_self error: %s", util.inspect(err))
              cb(err,null)
          g_http_server.emit("request",req,res)
          
      else
          throw  "Unsupported method:"+method
  catch exception
      console.log("http_request_self exception: %s", util.inspect(exception))
      cb(exception,null)

   
exports.encrypt_password = (khbh,password,cb) ->
  http_request "POST", config.services.hs.encrypt, {password}, (err,result) ->
    if err
      cb "加密失败: #{err}"
    else if not result.encrypted
      cb "加密失败"
    else
      cb null,khbh,result.encrypted


exports.encrypt_password2 = (password) ->
  Q.nfcall(http_request, "POST", config.services.hs.encrypt, {password})
    .then (result) ->
      unless result?.encrypted
        throw get_error_info("encrypt_fail",result)
      return result.encrypted
      


exports.verify_password_fail_msg = verify_password_fail_msg = "交易密码验证失败"

exports.verify_password = verify_password = (khbh,password,cb) ->
  http_request "GET", config.services.security.login, {password,client_id:khbh}, (err, result) ->
    if err
      console.error "verify_password error: #{JSON.stringify err}"
      cb verify_password_fail_msg
    else if result?.error_no == 0 and result.data?[0]?.fund_account and result.data?[0]?.branch_no
      cb null,khbh,password,result.data[0].fund_account,result.data[0].branch_no
    else
      cb verify_password_fail_msg


g_pinyin_db = null

load_pinyin_db = () ->
  db = {}
  lines = (l.trim().split(",")[0] for l in fs.readFileSync("#{__dirname}/../data/dict.txt").toString().split("\n"))
  for l in lines
    db[l[0]] = l[1...(l.length-1)] #最后一个字符是数字表示音调,去掉
  return db

lookup_pinyin = (char) ->
  if char of g_pinyin_db
    return g_pinyin_db[char]
  else
    return ""

exports.translate_to_pinyin = (string) ->
  unless g_pinyin_db
    g_pinyin_db = load_pinyin_db()
  return (lookup_pinyin(c) for c in string).join("")

exports.translate_to_pinyin_first_letter = (string) ->
  unless g_pinyin_db
    g_pinyin_db = load_pinyin_db()
  return (lookup_pinyin(c)[0] for c in string).join("")
                            
exports.resErr = resErr = (res,err,status=500) ->
  status = err.status if err.status
  if typeof(err) == "object" and err.error_no and err.error_info
    res.send(status, {error: err.error_info, error_no: err.error_no})
  else if typeof(err) == "object" and err.msg and err.code
    res.send(status,{ error: err.msg,error_no: err.code })
  else
    res.send(status,{ error: err.message ? err.toString() })

exports.md5 = md5 = (content) ->
  _md5 = crypto.createHash 'md5'
  _md5.update content
  return _md5.digest 'hex'

exports.return_error = (res,err) ->

  if err.code and err.code == get_error_info("require_trade_account").code
    return resErr(res,"用户未绑定交易账号",402)

  if err.code and err.code == get_error_info("require_trade_password").code
    return resErr(res,"需提供交易密码",405)

  return resErr res,err


exports.now = () ->
  return new Date().toFormat("YYYY-MM-DD HH:MI:SS")

# 对attr属性字段, 按照attr_list的顺序，把objs排序
exports.sort_by_attr_list = (objs, attr, attr_list) ->
  rank = {}
  for a, r in attr_list
    rank[a] = r
  return objs.sort (x,y) ->
    rx = rank[x[attr]]
    ry = rank[y[attr]]
    if rx < ry
      return -1
    else if rx > ry
      return 1
    else
      return 0


exports.yesterday = () ->
  return new Date((new Date()).getTime() - 24 * 3600 * 1000)

exports.fail = (res) ->
  (err) ->
    if err instanceof Error
      err = err.message
    else if err and err instanceof Object
      if err.error_info
        err = err.error_info
      if err.message
        err = err.message

    if typeof(err) == "string"
      return resErr res, err
    else
      return resErr res, JSON.stringify(err)
      

exports.success = (res,data) ->
  (data) ->
    if data == null
      return res.json {success:"true"}
    else
      return res.json data

# 兼容1.0数据格式
exports.successv1 = (res) ->
  (data) ->
    if data == null
      return res.json {success:"true"}
    else
      if data.data
        data.datas = data.data
        delete data.data
      return res.json data



exports.default_gfsrc = () ->
  site: 'x'
  page: 'x'
  position: 'x'
  landing: 'x'

g_default_gfsrc = exports.default_gfsrc()
    


exports.strip = (obj) ->
  result = {}
  for own k,v of obj
    if typeof v == "string"
      result[k] = v.trim()
    else
      result[k] = v
  return result

exports.merge = (objs...) ->
  result = {}
  for obj in objs
    for own k,v of obj
      result[k] = v
  return result

exports.split_fields = (dict_data) ->
    fields = []
    data = []
    for k,v of dict_data
      fields.push k
      data.push v  
    return [fields,data]
    
exports.convert_table_to_obj = (datas, fields) ->
  for data in datas
    obj = {}
    for field,i in fields
      obj[field] = data[i]
    obj
  

  
exports.is_dtf = (code) ->
  return (code of g_dtf2) or (code of g_dtf4)

# promise version of reduce        
exports.preduce = preduce = (list, init_val, fn) ->
  fn_list = list.map (x) ->
    (acc) -> fn(acc,x)
  result = Q(init_val);  
  fn_list.forEach (fn) ->
      result = result.then fn
  return result

exports.series = (list, fn) ->
  preduce(list,null, (acc,x) -> fn(x))



escape_regexp = (input) ->
  return input.replace(/([\\\^\$\+\-\.\?\[\]\{\}\(\)])/g,'\\$1')
                
exports.new_regexp = (input) ->
  return new RegExp(escape_regexp(input))
                   
exports.paginator = (items,page_num,page_size) ->

  page_count = Math.ceil(items.length / page_size)  # assert page_size > 0
  count = items.length

  result = 
    pageNum: page_num
    pageSize: page_size
    pageCount: page_count
    count: count
    items: items.slice (page_num-1)*page_size, (page_num)*page_size

  return result

exports.get_page_num_size = (req, default_page_size=20, page_size_name="pageSize", default_page_num=1, page_num_name="pageNum") ->
  page_num = Number(req.param(page_num_name) ? default_page_num)
  if !page_num or page_num <= 0
    page_num = default_page_num
    
  page_size = Number(req.param(page_size_name) ? default_page_size)
  if !page_size or page_size <= 0
    page_size = default_page_size
  return [page_num,page_size]



qmemo_cache = require("../models/cache").QmemoCache

lookup_cache = (cache_id, cache) ->
  Q.ninvoke(cache,"load",cache_id)

save_cache = (cache_id,cache_obj,seconds,cache) ->
  Q.ninvoke(cache,"save",cache_id,cache_obj,new Date(new Date().getTime() + 1000 * seconds))
    .then ->
      cache_obj

exports.qmemo = (seconds,prefix,fn) ->
  (args...) ->
    cache_id = prefix + (JSON.stringify(x) for x in args).join("_")
    lookup_cache(cache_id, qmemo_cache)
      .then (data) ->
        if data in [null, undefined]
          result = fn.apply(this,args)
          if result and typeof(result.then) == "function"
            result.then (result2) ->
              save_cache(cache_id,result2,seconds,qmemo_cache)
          else
            save_cache(cache_id,result,seconds,qmemo_cache)
        else
          console.debug "hit cache: #{cache_id}" 
          return data

  


exports.timing = (fn,args...) ->
  t1 = new Date
  result = fn.apply(this,args)
  if typeof(result?.then) == "function"
    result.then (result) ->
      t2 = new Date
      console.debug "[#{t2-t1}ms]: #{JSON.stringify result}"
      return [result,t2-t1]
  else
    t2 = new Date
    console.debug "[#{t2-t1}ms]: #{JSON.stringify result}"
    Q([result,t2-t1])


exports.memo = (seconds,fn) ->
  do ->
    cache = {}  # args -> {result, time}
    (args...) ->
      args_id = JSON.stringify(args)
      cache_entry = cache[args_id]
      if cache_entry and cache_entry.time and ((new Date) - cache_entry.time) < (seconds * 1000)
        console.debug "hit cache"
        return Q(cache_entry.result)
      else
        result = fn.apply(this,args)
        if typeof(result?.then) == "function"
          result.then (result2) ->
            cache[args_id] = {result,time:new Date()}
            return result2
        else
          cache[args_id] = {result,time:new Date()}
          return Q(result)

exports.get_paginator_params = (req, dflt_page_num = 1, dflt_page_size = 20) ->
  
  page_num = Number(req.param("pageNum") ? 1)
  if !page_num or page_num <= 0
    page_num = dflt_page_num

  page_size = Number(req.param("pageSize") ? 20)
  if !page_size or page_size <= 0
    page_size = dflt_page_size

  return [page_num, page_size]
 




# concate s1 and s1, remove duplicate char
# assert union_char("1234","12345") == "12345"
# assert union_char("1234","") == "1234"
# assert union_char("1234","1234") == "1234"
# assert union_char("1234","5") == "12345"

exports.union_char = (s1, s2) ->
  s1 = s1 or ""
  s2 = s2 or ""
  s = s1

  for c in s2
    unless s.indexOf(c)>=0
      s = s + c
  return s

# remove s2 from s1
# assert diff_char("1234","1") == "234"
# assert diff_char("","1") == ""
# assert diff_char("1234","1234") == ""
# assert diff_char("1234","536") == "124"

exports.diff_char = (s1, s2) ->
  s1 = s1 or ""
  s2 = s2 or ""

  s = ""
  for c in s1
    if s2.indexOf(c) < 0
      s += c
  return s
    

# 通过身份证获取性别 =>  "male", "female" 
# 15位身份证号码：第7、8位为出生年份(两位数)，第9、10位为出生月份，第11、12位代表出生日期，第15位代表性别，奇数为男，偶数为女。
# 18位身份证号码：第7、8、9、10位为出生年份(四位数)，第11、第12位为出生月份，第13、14位代表出生日期，第17位代表性别，奇数为男，偶数为女。
exports.gender_from_id = (id) ->
  if id.length == 15
    x = id[14]
  else if id.length == 18
    x = id[16]
  else
    return "unknown"

  if Number(x) % 2 == 0
    return "female"
  else
    return "male"

 


exports.get_request_src = (req) ->
  if req
    session = req.session or req.__gf_session
    if session
      return session.gfsrc or g_default_gfsrc
    else
      return g_default_gfsrc
  else
    return g_default_gfsrc

 
exports.session_cookie_name = () ->

  if process.env.NODE_ENV and process.env.NODE_ENV.indexOf("development")>=0 
    return 'gfportaltestsid'
  else if (process.env.NODE_ENV == "uat")
    return 'gfportaluatsid'
  else 
    return 'gfportalsid'
  


  
exports.analyze_sort_criteria = (sort_field,default_criteria={},default_order="desc") ->
  
  unless sort_field
    return default_criteria

  criteria = {}

  sorts = sort_field.split(',')
  for x in sort_field.split(",")
    
    [field,order] = x.split("|")
    
    unless order
      order = default_order

    if order == "desc"
      criteria[field] = -1
    else
      criteria[field] = 1

  return criteria

 

exports.set_req_attr = (req, obj) ->
  if req.method == "POST"
    pos = "body"
  else
    pos = "query"

  for own k,v of obj
    req[pos][k] = v
    


# mark str[start..end] as *
exports.hide_string = (str, start, end) ->
  result = ""

  i = 0
  while i < start
    result += str[i]
    i++

  while i <= end
    result += "*"
    i++

  while i < str.length
    result += str[i]
    i++ 
    
  return result


is_object = (x) ->
  return _.isObject(x) and not _.isArray(x)


deep_extend_one = (dest,source) ->
  
  unless is_object(dest) and is_object(source)
    return dest

  for own k,v of source
    if k of dest
      dv = dest[k]
      if is_object(dv) and is_object(v)
        dest[k] = deep_extend_one(dv,v)
      else
        dest[k] = v
    else
      dest[k] = v

  return dest

  

exports.deep_extend = deep_extend = (dest, sources...) ->

  if sources.length == 0
    return dest

  sources[0] = deep_extend_one(dest,sources[0])
  return deep_extend.apply null, sources


exports.get_user_id = (req) ->
  user = req.session.user
  return "#{user.portal?.id}_#{user.trade?.khbh}"
 

host_signature = ->
  interfaces = os.networkInterfaces()
  for k,v of interfaces
    for address in v
      if (address.family == 'IPv4' and !address.internal) 
        x = address.address.split(".")
        return String(Number(x.join("")) % 595967 + "_" + x[x.length-1]) 
  return ""


g_host_signature = host_signature();

exports.genuuid = ->
  return uuid.v1() + "_" + process.pid + "_" + g_host_signature;
